package com.dhecp.project.flowable.controller;

import com.dhecp.common.annotation.Log;
import com.dhecp.common.constant.Constants;
import com.dhecp.common.core.controller.BaseController;
import com.dhecp.common.core.domain.AjaxResult;
import com.dhecp.common.core.page.TableDataInfo;
import com.dhecp.common.enums.BusinessType;
import com.dhecp.common.utils.MessageUtils;
import com.dhecp.common.utils.StringUtils;
import com.dhecp.common.utils.TypeUtils;
import com.dhecp.core.constant.ModuleTable;
import com.dhecp.core.enums.FunctionForFlowable;
import com.dhecp.core.layui.GridColumnsPropertyModel;
import com.dhecp.project.flowable.domain.ActProcessModel;
import com.dhecp.project.flowable.extend.CustomBpmnJsonConverter;
import com.dhecp.project.flowable.extend.UserRepresentation;
import com.dhecp.project.flowable.service.ActProcessModelService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.editor.constants.ModelDataJsonConstants;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.repository.Model;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ProcessValidatorFactory;
import org.flowable.validation.ValidationError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @ClassName ActProcessModelController
 * @Description TODO	流程模型控制层
 * @Author alula
 * @Date 2020-04-01 14:18
 */
@Controller
@RequestMapping("/flowable/model")
public class ActProcessModelController extends BaseController {
    private String prefix = "flowable/model";
    private String moduleTable = ModuleTable.ACT_RE_MODEL;

    @Autowired
    private ActProcessModelService actProcessModelService;

    @Autowired
    private ObjectMapper objectMapper;
    
    @Autowired
    private GridColumnsPropertyModel gridColumnsPropertyModel;
    
    @RequiresPermissions("flowable:model:view")
    @GetMapping()
    public ModelAndView model(@RequestParam Map<String, Object> params)
    {
    	params.put("moduleTable", moduleTable);
    	params.put("entity", new ActProcessModel());

    	Integer toolBarWidth = 350;
        params.put("toolBarWidth", toolBarWidth);
    	
    	ModelAndView mv = this.getModelAndView();
    	mv.addObject("gridColumns", gridColumnsPropertyModel.queryGridColumnsPropertyModel(params));
    	mv.addObject("prefix", prefix);
        mv.addObject("moduleTable", moduleTable);
    	mv.setViewName(prefix + "/model");
		return mv;
    }

    @RequestMapping("/list")
    @RequiresPermissions("flowable:model:list")
    @ResponseBody
    public TableDataInfo list(@RequestParam Map<String, Object> params) {
        List<Model> list = actProcessModelService.queryModelList(params);
        return getDataTable(list);
    }

    @RequiresPermissions("flowable:model:add")
    @RequestMapping("/add")
    public String add() {
        return prefix + "/model_add";
    }

    @RequiresPermissions("flowable:model:add")
    @Log(title = "流程模型增加", businessType = BusinessType.INSERT)
    @RequestMapping(value = "/addSave")
    @ResponseBody
    public AjaxResult addModel(@RequestParam("name") String name, @RequestParam("key") String key,
                               @RequestParam(value = "category", required = false) String category,
                               @RequestParam(value = "description", required = false) String description) {
        AjaxResult ajaxResult = new AjaxResult();
        try{
            Model model = actProcessModelService.getNewModel();
            description = StringUtils.isEmpty(description) ? category:description;
            //流程版本
            int revision = 1;
            ObjectNode modelNode = objectMapper.createObjectNode();
            modelNode.put("key", key);
            modelNode.put(ModelDataJsonConstants.MODEL_ID, model.getId());
            modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
            modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
            modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);
            model.setName(name);
            model.setKey(key);
            model.setMetaInfo(modelNode.toString());
            model.setCategory(category);
            ajaxResult = actProcessModelService.addModel(model, description);
            return  ajaxResult;
        }catch (Exception e){
            return AjaxResult.error(this.getExceptionMessage(e));
        }
    }

    @RequiresPermissions("flowable:model:edit")
    @Log(title = "流程模型修改", businessType = BusinessType.UPDATE)
    @RequestMapping(value="/editor/save/{modelId}")
    @ResponseBody
    public AjaxResult editModel(@PathVariable String modelId, @RequestParam String name, @RequestParam String description,
                                @RequestParam String json_xml) {
        AjaxResult ajaxResult = new AjaxResult();
        try{
            Model model = actProcessModelService.queryModelById(modelId);
            ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
            modelJson.put("key", model.getKey());
            modelJson.put(ModelDataJsonConstants.MODEL_ID, model.getId());
            modelJson.put(ModelDataJsonConstants.MODEL_NAME, name);
            modelJson.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
            model.setMetaInfo(modelJson.toString());
            model.setName(name);
            ajaxResult = actProcessModelService.updateModel(model, json_xml, "");
            return  ajaxResult;
        }catch (Exception e){
            return AjaxResult.error(this.getExceptionMessage(e));
        }
    }

    @RequiresPermissions("flowable:model:delete")
    @Log(title = "流程模型删除", businessType = BusinessType.DELETE)
    @RequestMapping("/delete")
    @ResponseBody
    public AjaxResult deleteModel(String ids) {
        AjaxResult ajaxResult = new AjaxResult();
        try {
            ajaxResult = actProcessModelService.deleteModelByIds(ids);
            return ajaxResult;
        }
        catch (Exception e) {
            return AjaxResult.error(this.getExceptionMessage(e));
        }
    }

    @RequiresPermissions("flowable:model:edit")
    @Log(title = "流程模型部署", businessType = BusinessType.UPDATE)
    @RequestMapping(value = "/deploy/{modelId}")
    @ResponseBody
    public AjaxResult deployModel(@PathVariable("modelId") String modelId, RedirectAttributes redirectAttributes) {
        AjaxResult ajaxResult = new AjaxResult();
        try {
            ajaxResult = actProcessModelService.deployModel(modelId);
            return ajaxResult;
        }
        catch (Exception e) {
            return AjaxResult.error(this.getExceptionMessage(e));
        }
    }

    @RequestMapping(value = "/export/{modelId}")
    public void exportModel(@PathVariable("modelId") String modelId, HttpServletResponse response) {
        try {
            Model modelData = actProcessModelService.queryModelById(modelId);
            BpmnJsonConverter jsonConverter = new CustomBpmnJsonConverter();
            JsonNode editorNode = new ObjectMapper().readTree(actProcessModelService.getModelEditorSource(modelData.getId()));
            BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
            BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
            byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);
            ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
            String filename = bpmnModel.getMainProcess().getId() + ".bpmn20.xml";
            response.setContentType("application/octet-stream;charset=UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
            IOUtils.copy(in, response.getOutputStream());
            response.flushBuffer();
        } catch (Exception e) {
            try {
                response.sendRedirect("/flowable/model");
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }


    /**
     * 部署流程定义
     */
    @RequiresPermissions("flowable:model:add")
    @Log(title = "流程模型文件导入", businessType = BusinessType.INSERT)
    @RequestMapping("/importSave")
    @ResponseBody
    public AjaxResult importModel(@RequestParam("processModel") MultipartFile file){
        AjaxResult ajaxResult = new AjaxResult();
        try {
            if (!file.isEmpty()) {
                String fileName = file.getOriginalFilename();
                String extension = FilenameUtils.getExtension(fileName);
                if ("xml".equalsIgnoreCase(extension)) {
                    if (!file.getOriginalFilename().contains("bpmn20.xml")) {
                        return AjaxResult.error(MessageUtils.message("flowable.model.error.uploadallowtype"));
                    }
                }
                if (!"xml".equalsIgnoreCase(extension)
                        && !"bpmn".equalsIgnoreCase(extension)
                        && !"zip".equalsIgnoreCase(extension)
                        && !"bar".equalsIgnoreCase(extension)) {
                    return AjaxResult.error(MessageUtils.message("flowable.model.error.uploadallowtype"));
                }
                String editor =new String(file.getBytes(), Constants.UTF8);
                ajaxResult = actProcessModelService.importModel(editor, fileName);
                return  ajaxResult;
            }
            return AjaxResult.error(MessageUtils.message("module.error.iserror"));
        }
        catch (Exception e) {
            return AjaxResult.error(this.getExceptionMessage(e));
        }
    }

    @RequestMapping(value = "/editor/account", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public UserRepresentation getAccount() {
        UserRepresentation userRepresentation = new UserRepresentation();
        userRepresentation.setId("admin");
        userRepresentation.setEmail("admin@flowable.org");
        userRepresentation.setFullName("Administrator");
        userRepresentation.setLastName("admin");
        userRepresentation.setFirstName("admin");
        List<String> privileges = new ArrayList<>();
        privileges.add("access-modeler");
        privileges.add("access-idm");
        privileges.add("access-admin");
        privileges.add("access-task");
        privileges.add("access-rest-api");
        userRepresentation.setPrivileges(privileges);
        return userRepresentation;
    }

    @RequestMapping(value="/editor/stencil", produces = "application/json;charset=utf-8")
    @ResponseBody
    public String getStencilset() {
        InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset/stencilset_bpmn.json");
        try {
            return IOUtils.toString(stencilsetStream, "utf-8");
        } catch (Exception e) {
            throw new FlowableException("加载stencilset错误", e);
        }
    }

    @RequestMapping(value = "/editor/json/{modelId}", produces = "application/json")
    @ResponseBody
    public ObjectNode getEditorJson(@PathVariable String modelId) {
        ObjectNode modelNode = actProcessModelService.queryWrapModelById(modelId);
        return modelNode;
    }

    @RequestMapping(value = "/editor/validate", consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public List<ValidationError> validate(@RequestBody JsonNode body){
        if (body != null && body.has("stencilset")) {
            JsonNode stencilset = body.get("stencilset");
            if (stencilset.has("namespace")) {
                String namespace = stencilset.get("namespace").asText();
                if (namespace.endsWith("bpmn2.0#")) {
                    BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(body);
                    ProcessValidator validator = new ProcessValidatorFactory().createDefaultProcessValidator();
                    List<ValidationError> errors = validator.validate(bpmnModel);
                    return errors;
                }
            }
        }
        return Collections.emptyList();
    }

    @RequestMapping("/editor/formList")
    @ResponseBody
    public Map<String,Object> formList(@RequestParam Map<String, Object> params) {
        Map<String,Object> dataLimitMap = TypeUtils.EnumToMap(FunctionForFlowable.class);
        return dataLimitMap;
    }
}
